#define _CRT_SECURE_NO_WARNINGS 1

// 预处理详解



// 预定义符号
// __FILE__    //进行编译的源文件
// __LINE__    //文件当前的行号
// __DATE__    //文件被编译的日期
// __TIME__    //文件被编译的时间
// __STDC__    //如果编译器遵循ANSI C，其值为1，否则未定义

//#include <stdio.h>
//
//extern void Test();
//
//int main()
//{
//	//printf("file:%s line:%d\n", __FILE__, __LINE__);
//	//Test();
//
//	printf("%s\n", __FILE__);
//	printf("%s\n", __DATE__);
//	printf("%s\n", __TIME__);
//	printf("%s\n", __LINE__);
//	// printf("%s\n", __STDC__); // err 说明VS不支持ANSI C
//
//	return 0;
//}

// 尽量不要在宏后面打分号
// #define定义常量
// #define MAX 1000
// #define reg register          //为 register这个关键字，创建一个简短的名字
// #define do_forever for(;;)     //用更形象的符号来替换一种实现
// #define CASE break;case        //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长，可以分成几行写，除了最后一行外，每行的后面都加一个反斜杠(续行符)。
// #define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ ,       \
__DATE__,__TIME__ )  
// 想明白了，编程就是一门艺术

// define定义宏
// define机制包括了一个规定，允许把参数替换到文本中，这种实现通常称为宏或定义宏
// #define name(parament-list) stuff
// #define SQUARE(x) (x) * (x)

//   宏的参数不运算，直接替换到宏的体内
// 1.宏的参数中如果有操作符，和宏的内容中的操作符
//   因为运算符有优先级的问题，可能导致运算顺序不达预期
//   所以容易产生问题

// 2.宏在书写的时候，给参数都带上括号，不要吝啬括号
//   用于对数值表达式进行求值的宏定义都应该用这种方式加上括号
//   避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用


// 带有副作用的宏参数
//#include <stdio.h>
//
//#define MAX(x, y) ((x)>(y)?(x):(y))
//
//int main()
//{
//	int a = 0, b = 0;
//	scanf("%d %d", &a, &b); // 3  5
//
//	int m = MAX(a++, b++);
//	// int m = ((a++)>(b++)?(a++):(b++));
//
//	printf("m = %d\n", m);
//	printf("a = %d\n", a);
//	printf("b = %d\n", b);
//
//	return 0;
//}

// 宏替换的规则
// 在调用宏时，首先对参数进行检查，如果是，它们首先被替换
// 替换文本随后被插入到程序中原来文本的位置，对于宏，参数名被他们的值替换
// 最后，再次对结果文件进行扫描，看看是否包含任何#define定义的符号

// 请注意：宏参数和#define定义中可以出现其他#define定义的符号，对于宏，不能出现递归
//		  当预处理器搜索#define定义的符号的时候，字符串常量的内容并不被搜索，比如说printf("符号")；


// 宏和函数很像，它们之间的对比：
// 宏通常被应用于执行简单的运算
// 因为这时候宏就负责运算，函数要函数调用运算函数返回
// 相比之下宏更优秀一些
// 宏的参数是与类型无关的，这可以使其获得更为广泛的应用

// 宏的劣势有哪些？
// 每次使用宏的时候，一份宏定义的代码将插入到程序中，除非宏比较短，否则可能大幅度增加代码的长度
// 宏是没法调试的
// 宏由于类型无关，也就不够严谨
// 宏可能会带来运算符优先级的问题，导致程序容易出错

// 宏有时候可以做函数做不到的事情，这函数做不到
// 两个双引号组成的字符串，可以连在一起
//int main()
//{
//	printf("hello world\n");
//	printf("hello ""world\n");
//
//	return 0;
//}

// #运算符号
// #运算符将宏的一个参数转换成字符串字面量，它仅允许出现在带参数的宏的替换列表中
// #运算符所执行的操作可以理解为字符串化
// #val的作用是让val不要解析成值
//#define PRINT(val, format)  printf("the value of "#val" is " format"\n", val)
////
//int main()
//{
//	int a = 10;
//	//printf("the value of a is %d\n", a);
//	PRINT(a, "%d");
//	//printf("the value of ""a"" is ""%d""\n", a);
//
//	int b = 20;
//	//printf("the value of b is %d\n", b);
//	PRINT(b, "%d");
//
//	float f = 3.5f;
//	//printf("the value of f is %f\n", f);
//	PRINT(f, "%f");
//
//	return 0;
//}

// 泛型编程的思想！
// 使用宏：定义的一个通用的函数模具
// ## 的作用
// ##可以把位于它两边的符号合成一个符号。
// 它允许宏定义从分离的文本片段创建标识符。

//#define GENRIC_MAX(type)\
//type type##_max(type x, type y)\
//{\
//	return (x > y ? x : y);\
//}

// 命名约定
// 函数和宏的使用语法很相似，语言没办法完全区分二者
// 我们平时的习惯是：一般来说，把宏名全部大写，函数名不要全部大写


// #undef NAME
// 这条指令用于移除一个宏定义
// 如果现存的一个名字需要被重新定义，那么它的旧名字首先要被移除

//#include <stdio.h>
//#define MAX 100
//
//int main()
//{
//	printf("%d\n", MAX);
//#undef MAX
//	// printf("%d\n", MAX); // err
//#define MAX 100
//	printf("%d\n", MAX);
//
//	return 0;
//}

// 命令行定义
// 许多C的编译器提供了一种能力，允许在命令行中定义符号，用于启动编译过程
// 当我们根据同一个源文件要编译出一个程序的不同版本的时候，这个特性有点用处
// 假设某个程序中声明了一个某个长度的数组，如果机器内存有限，我们需要一个很小的数组
// 但是另外一个机器内存大些，我们需要一个数组能够大些

// #include <stdio.h>
// int main()
// {
//	int array[ARRAY_SIZE];
// 	int i = 0;
//	for (i = 0; i < ARRAY_SIZE; i++)
//	{
//		array[i] = i;
//	}
//	for (i = 0; i < ARRAY_SIZE; i++)
//	{
//		printf("%d ", array[i]);
//	}
//	printf("\n");
//	return 0;
// }
// linux 环境演示
// gcc - D ARRAY_SIZE = 10 programe.c


// 条件编译
// 满足条件就编译，不满足条件就不编译
// 在编译一个程序的时候，如果我们要将一条语句（一组语句）编译或者放弃是很方便的
// 因为我们有条件编译指令
// 比如调试性的代码，删除可惜，保留碍事，所以我们可以选择性的编译

//#include <stdio.h>
//#define __DEBUG__
//
//int main()
//{
//	int i = 0;
//	int arr[10] = { 0 };
//
//	for (int i = 0; i < 10; i++) {
//		arr[i] = i;
//#ifdef __DEBUG__
//		printf("%d\n", arr[i]); // 为了观察数组是否赋值成功
//#endif // __DEBUG__
//	}
//
//	return 0;
//}

// 常见的条件编译指令
// #if 常量表达式(真就编译，假就不编)
// ...
// #endif

//#include <stdio.h>
//#define M 3
//
//int main()
//{
//	int a = 3;
//#if M == a // 预处理阶段就已经判断过，这时候没有a
//	printf("hehe\n");
//#endif
//
//	return 0;
//}

// 多分支的条件编译
// #if 常量表达式
// ...
// #elif 常量表达式
// ...
// #else 
// ...
// #endif

// 判断是否被定义
//#if defined(symbol)
//#ifdef symbol
//
//#if !defined(symbol)
//#ifndef symbol


// 头文件的包含
//#include <stdio.h>
//#include "filename"
// 查找策略：先在源文件所在目录查找，如果该头文件未找到
//		    编译器就像查找函数头文件一样在标准位置查找头文件，找不到就提示编译错误
//          所以说，“”也可以查找库函数，只不过效率会变低


// 嵌套文件包含
// 如果头文件重复包含，那么会被多次编译
// 如果test.h文件比较大，这样预处理后代码量会剧增
// 这时候可以在头文件当中
//#ifndef __TEST_H
//#define __TEST_H
//
//int Add(int x, int y);
//
//#endif
// 或者#pragma once


